/*
  Copyright (c) 2009 Thomas McGuire <mcguire@kde.org>

  Based on KMail and libkdepim code by:
  Copyright 2007 - 2012 Laurent Montel <montel@kde.org>

  This library is free software; you can redistribute it and/or modify it
  under the terms of the GNU Library General Public License as published by
  the Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This library is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
  License for more details.

  You should have received a copy of the GNU Library General Public License
  along with this library; see the file COPYING.LIB.  If not, write to the
  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  02110-1301, USA.
*/
#include "textedit.h"

#include "emailquotehighlighter.h"
#include "emoticontexteditaction.h"
#include "inserthtmldialog.h"
#include "tableactionmenu.h"
#include "insertimagedialog.h"

#include <kmime/kmime_codecs.h>

#include <KDE/KAction>
#include <KDE/KActionCollection>
#include <KDE/KCursor>
#include <KDE/KFileDialog>
#include <KDE/KLocalizedString>
#include <KDE/KMessageBox>
#include <KDE/KPushButton>
#include <KDE/KUrl>
#include <KDE/KImageIO>
#include <KDE/KIcon>

#include <QtCore/QBuffer>
#include <QtCore/QDateTime>
#include <QtCore/QMimeData>
#include <QtCore/QFileInfo>
#include <QtCore/QPointer>
#include <QKeyEvent>
#include <QTextLayout>

#include "textutils.h"
#include <QPlainTextEdit>

namespace KPIMTextEdit {

class TextEditPrivate
{
  public:

    TextEditPrivate( TextEdit *parent )
      : actionAddImage( 0 ),
        actionDeleteLine( 0 ),
        actionAddEmoticon( 0 ),
        actionInsertHtml( 0 ),
        actionTable( 0 ),
        actionFormatReset( 0 ),
        q( parent ),
        imageSupportEnabled( false ),
        emoticonSupportEnabled( false ),
        insertHtmlSupportEnabled( false ),
        insertTableSupportEnabled( false ),
        spellCheckingEnabled( false )
    {
    }

    /**
     * Helper function for addImage(), which does the actual work of adding the QImage as a
     * resource to the document, pasting it and adding it to the image name list.
     *
     * @param imageName the desired image name. If it is already taken, a number will
     *                  be appended to it
     * @param image the actual image to add
     */
    void addImageHelper( const QString &imageName, const QImage &image,
                         int width = -1, int height = -1 );

    /**
     * Helper function to get the list of all QTextImageFormats in the document.
     */
    QList<QTextImageFormat> embeddedImageFormats() const;

    /**
     * Removes embedded image markers, converts non-breaking spaces to normal spaces
     * and other fixes for strings that came from toPlainText()/toHtml().
     */
    void fixupTextEditString( QString &text ) const;

    /**
     * Does the constructor work
     */
    void init();

    /**
     * Opens a file dialog to let the user choose an image and then pastes that
     * image to the editor
     */
    void _k_slotAddImage();

    void _k_slotDeleteLine();

    void _k_slotAddEmoticon( const QString & );

    void _k_slotInsertHtml();

    void _k_slotFormatReset();

    void _k_slotTextModeChanged( KRichTextEdit::Mode );

    /// The action that triggers _k_slotAddImage()
    KAction *actionAddImage;

    /// The action that triggers _k_slotDeleteLine()
    KAction *actionDeleteLine;

    EmoticonTextEditAction *actionAddEmoticon;

    KAction *actionInsertHtml;

    TableActionMenu *actionTable;

    KAction *actionFormatReset;

    /// The parent class
    TextEdit *q;

    /// Whether or not adding or pasting images is supported
    bool imageSupportEnabled;

    bool emoticonSupportEnabled;

    bool insertHtmlSupportEnabled;

    bool insertTableSupportEnabled;
    /**
     * The names of embedded images.
     * Used to easily obtain the names of the images.
     * New images are compared to the the list and not added as resource if already present.
     */
    QStringList mImageNames;

    /**
     * Although KTextEdit keeps track of the spell checking state, we override
     * it here, because we have a highlighter which does quote highlighting.
     * And since disabling spellchecking in KTextEdit simply would turn off our
     * quote highlighter, we never actually deactivate spell checking in the
     * base class, but only tell our own email highlighter to not highlight
     * spelling mistakes.
     * For this, we use the KTextEditSpellInterface, which is basically a hack
     * that makes it possible to have our own enabled/disabled state in a binary
     * compatible way.
     */
    bool spellCheckingEnabled;

    QString configFile;
    QFont saveFont;
};

} // namespace

using namespace KPIMTextEdit;

void TextEditPrivate::fixupTextEditString( QString &text ) const
{
  // Remove line separators. Normal \n chars are still there, so no linebreaks get lost here
  text.remove( QChar::LineSeparator );

  // Get rid of embedded images, see QTextImageFormat documentation:
  // "Inline images are represented by an object replacement character (0xFFFC in Unicode) "
  text.remove( 0xFFFC );

  // In plaintext mode, each space is non-breaking.
  text.replace( QChar::Nbsp, QChar::fromLatin1( ' ' ) );
}

TextEdit::TextEdit( const QString &text, QWidget *parent )
  : KRichTextWidget( text, parent ),
    d( new TextEditPrivate( this ) )
{
  d->init();
}

TextEdit::TextEdit( QWidget *parent )
  : KRichTextWidget( parent ),
    d( new TextEditPrivate( this ) )
{
  d->init();
}

TextEdit::TextEdit( QWidget *parent, const QString &configFile )
  : KRichTextWidget( parent ),
    d( new TextEditPrivate( this ) )
{
  d->init();
  d->configFile = configFile;
}

TextEdit::~TextEdit()
{
}

bool TextEdit::eventFilter( QObject *o, QEvent *e )
{
#ifndef QT_NO_CURSOR
  if ( o == this ) {
    KCursor::autoHideEventFilter( o, e );
  }
#endif
  return KRichTextWidget::eventFilter( o, e );
}

void TextEditPrivate::init()
{
  q->connect( q, SIGNAL(textModeChanged(KRichTextEdit::Mode)),
              q, SLOT(_k_slotTextModeChanged(KRichTextEdit::Mode)) );
  q->setSpellInterface( q );
  // We tell the KRichTextWidget to enable spell checking, because only then it will
  // call createHighlighter() which will create our own highlighter which also
  // does quote highlighting.
  // However, *our* spellchecking is still disabled. Our own highlighter only
  // cares about our spellcheck status, it will not highlight missspelled words
  // if our spellchecking is disabled.
  // See also KEMailQuotingHighlighter::highlightBlock().
  spellCheckingEnabled = false;
  q->setCheckSpellingEnabledInternal( true );

#ifndef QT_NO_CURSOR
  KCursor::setAutoHideCursor( q, true, true );
#endif
  q->installEventFilter( q );
}

QString TextEdit::configFile() const
{
  return d->configFile;
}

void TextEdit::keyPressEvent ( QKeyEvent * e )
{
  if ( e->key() ==  Qt::Key_Return ) {
    QTextCursor cursor = textCursor();
    int oldPos = cursor.position();
    int blockPos = cursor.block().position();

    //selection all the line.
    cursor.movePosition( QTextCursor::StartOfBlock );
    cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
    QString lineText = cursor.selectedText();
    if ( ( ( oldPos - blockPos )  > 0 ) &&
         ( ( oldPos - blockPos ) < int( lineText.length() ) ) ) {
      bool isQuotedLine = false;
      int bot = 0; // bot = begin of text after quote indicators
      while ( bot < lineText.length() ) {
        if ( ( lineText[bot] == QChar::fromLatin1( '>' ) ) ||
             ( lineText[bot] == QChar::fromLatin1( '|' ) ) ) {
          isQuotedLine = true;
          ++bot;
        } else if ( lineText[bot].isSpace() ) {
          ++bot;
        } else {
          break;
        }
      }
      KRichTextWidget::keyPressEvent( e );
      // duplicate quote indicators of the previous line before the new
      // line if the line actually contained text (apart from the quote
      // indicators) and the cursor is behind the quote indicators
      if ( isQuotedLine &&
           ( bot != lineText.length() ) &&
           ( ( oldPos - blockPos ) >= int( bot ) ) ) {
        // The cursor position might have changed unpredictably if there was selected
        // text which got replaced by a new line, so we query it again:
        cursor.movePosition( QTextCursor::StartOfBlock );
        cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
        QString newLine = cursor.selectedText();

        // remove leading white space from the new line and instead
        // add the quote indicators of the previous line
        int leadingWhiteSpaceCount = 0;
        while ( ( leadingWhiteSpaceCount < newLine.length() ) &&
                newLine[leadingWhiteSpaceCount].isSpace() ) {
          ++leadingWhiteSpaceCount;
        }
        newLine = newLine.replace( 0, leadingWhiteSpaceCount, lineText.left( bot ) );
        cursor.insertText( newLine );
        //cursor.setPosition( cursor.position() + 2 );
        cursor.movePosition( QTextCursor::StartOfBlock );
        setTextCursor( cursor );
      }
    } else {
      KRichTextWidget::keyPressEvent( e );
    }
  } else {
    KRichTextWidget::keyPressEvent( e );
  }
}

bool TextEdit::isSpellCheckingEnabled() const
{
  return d->spellCheckingEnabled;
}

void TextEdit::setSpellCheckingEnabled( bool enable )
{
  EMailQuoteHighlighter *hlighter = dynamic_cast<EMailQuoteHighlighter*>( highlighter() );
  if ( hlighter ) {
    hlighter->toggleSpellHighlighting( enable );
  }

  d->spellCheckingEnabled = enable;
  emit checkSpellingChanged( enable );
}

bool TextEdit::shouldBlockBeSpellChecked( const QString &block ) const
{
  return !isLineQuoted( block );
}

bool KPIMTextEdit::TextEdit::isLineQuoted( const QString &line ) const
{
  return quoteLength( line ) > 0;
}

int KPIMTextEdit::TextEdit::quoteLength( const QString &line ) const
{
  bool quoteFound = false;
  int startOfText = -1;
  const int lineLength( line.length() );
  for ( int i = 0; i < lineLength; ++i ) {
    if ( line[i] == QLatin1Char( '>' ) || line[i] == QLatin1Char( '|' ) ) {
      quoteFound = true;
    } else if ( line[i] != QLatin1Char( ' ' ) ) {
      startOfText = i;
      break;
    }
  }
  if ( quoteFound ) {
    if ( startOfText == -1 ) {
      startOfText = line.length() - 1;
    }
    return startOfText;
  } else {
    return 0;
  }
}

const QString KPIMTextEdit::TextEdit::defaultQuoteSign() const
{
  return QLatin1String( "> " );
}

void TextEdit::createHighlighter()
{
  EMailQuoteHighlighter *emailHighLighter = new EMailQuoteHighlighter( this );

  setHighlighterColors( emailHighLighter );

  //TODO change config
  KRichTextWidget::setHighlighter( emailHighLighter );

  if ( !spellCheckingLanguage().isEmpty() ) {
    setSpellCheckingLanguage( spellCheckingLanguage() );
  }
  setSpellCheckingEnabled( isSpellCheckingEnabled() );
}

void TextEdit::setHighlighterColors( EMailQuoteHighlighter *highlighter )
{
  Q_UNUSED( highlighter );
}

QString TextEdit::toWrappedPlainText() const
{
    QTextDocument *doc = document();
    return toWrappedPlainText( doc );
}

QString TextEdit::toWrappedPlainText( QTextDocument *doc ) const
{
  QString temp;
  QRegExp rx( QLatin1String( "(http|ftp|ldap)s?\\S+-$" ) );
  QTextBlock block = doc->begin();
  while ( block.isValid() ) {
    QTextLayout *layout = block.layout();
    const int numberOfLine( layout->lineCount() );
    bool urlStart = false;
    for ( int i = 0; i < numberOfLine; ++i ) {
      QTextLine line = layout->lineAt( i );
      QString lineText = block.text().mid( line.textStart(), line.textLength() );

      if ( lineText.contains( rx ) ||
           ( urlStart && !lineText.contains( QLatin1Char( ' ' ) ) &&
             lineText.endsWith( QLatin1Char( '-' ) ) ) ) {
        // don't insert line break in URL
        temp += lineText;
        urlStart = true;
      } else {
        temp += lineText + QLatin1Char( '\n' );
      }
    }
    block = block.next();
  }

  // Remove the last superfluous newline added above
  if ( temp.endsWith( QLatin1Char( '\n' ) ) ) {
    temp.chop( 1 );
  }

  d->fixupTextEditString( temp );
  return temp;
}

QString TextEdit::toCleanPlainText( const QString &plainText ) const
{
  QString temp = plainText;
  d->fixupTextEditString( temp );
  return temp;
}

QString TextEdit::toCleanPlainText() const
{
  return toCleanPlainText( toPlainText() );
}

void TextEdit::createActions( KActionCollection *actionCollection )
{
  KRichTextWidget::createActions( actionCollection );

  if ( d->imageSupportEnabled ) {
    d->actionAddImage = new KAction( KIcon( QLatin1String( "insert-image" ) ),
                                     i18n( "Add Image" ), this );
    actionCollection->addAction( QLatin1String( "add_image" ), d->actionAddImage );
    connect( d->actionAddImage, SIGNAL(triggered(bool)), SLOT(_k_slotAddImage()) );
  }
  if ( d->emoticonSupportEnabled ) {
    d->actionAddEmoticon = new EmoticonTextEditAction( this );
    actionCollection->addAction( QLatin1String( "add_emoticon" ), d->actionAddEmoticon );
    connect( d->actionAddEmoticon, SIGNAL(emoticonActivated(QString)),
             SLOT(_k_slotAddEmoticon(QString)) );
  }

  if ( d->insertHtmlSupportEnabled ) {
    d->actionInsertHtml = new KAction( i18n( "Insert HTML" ), this );
    actionCollection->addAction( QLatin1String( "insert_html" ), d->actionInsertHtml );
    connect( d->actionInsertHtml, SIGNAL(triggered(bool)), SLOT(_k_slotInsertHtml()) );
  }

  if ( d->insertTableSupportEnabled ) {
    d->actionTable = new TableActionMenu( actionCollection, this );
    d->actionTable->setIcon( KIcon( QLatin1String( "table" ) ) );
    d->actionTable->setText( i18n( "Table" ) );
    d->actionTable->setDelayed( false );
    actionCollection->addAction( QLatin1String( "insert_table" ), d->actionTable );
  }

  d->actionDeleteLine = new KAction( i18n( "Delete Line" ), this );
  d->actionDeleteLine->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_K ) );
  actionCollection->addAction( QLatin1String( "delete_line" ), d->actionDeleteLine );
  connect( d->actionDeleteLine, SIGNAL(triggered(bool)), SLOT(_k_slotDeleteLine()) );

  d->actionFormatReset =
    new KAction( KIcon( QLatin1String( "draw-eraser" ) ), i18n( "Reset Font Settings" ), this );
  d->actionFormatReset->setIconText( i18n( "Reset Font" ) );
  actionCollection->addAction( QLatin1String( "format_reset" ), d->actionFormatReset );
  connect( d->actionFormatReset, SIGNAL(triggered(bool)), SLOT(_k_slotFormatReset()) );
}

void TextEdit::addImage( const KUrl &url, int width, int height )
{
  addImageHelper( url, width, height );
}

void TextEdit::addImage( const KUrl &url )
{
  addImageHelper( url );
}

void TextEdit::addImageHelper( const KUrl &url, int width, int height )
{
  QImage image;
  if ( !image.load( url.path() ) ) {
    KMessageBox::error(
      this,
      i18nc( "@info",
             "Unable to load image <filename>%1</filename>.",
             url.path() ) );
    return;
  }
  QFileInfo fi( url.path() );
  QString imageName =
    fi.baseName().isEmpty() ?
      QLatin1String( "image.png" ) :
      QString( fi.baseName() + QLatin1String( ".png" ) );
  d->addImageHelper( imageName, image, width, height );
}

void TextEdit::loadImage ( const QImage &image, const QString &matchName,
                           const QString &resourceName )
{
  QSet<int> cursorPositionsToSkip;
  QTextBlock currentBlock = document()->begin();
  QTextBlock::iterator it;
  while ( currentBlock.isValid() ) {
    for ( it = currentBlock.begin(); !it.atEnd(); ++it ) {
      QTextFragment fragment = it.fragment();
      if ( fragment.isValid() ) {
        QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
        if ( imageFormat.isValid() && imageFormat.name() == matchName ) {
          int pos = fragment.position();
          if ( !cursorPositionsToSkip.contains( pos ) ) {
            QTextCursor cursor( document() );
            cursor.setPosition( pos );
            cursor.setPosition( pos + 1, QTextCursor::KeepAnchor );
            cursor.removeSelectedText();
            document()->addResource( QTextDocument::ImageResource,
                                     QUrl( resourceName ), QVariant( image ) );
            QTextImageFormat format;
            format.setName( resourceName );
            if ( ( imageFormat.width() != 0 ) && ( imageFormat.height() != 0 ) ) {
              format.setWidth( imageFormat.width() );
              format.setHeight( imageFormat.height() );
            }
            cursor.insertImage( format );

            // The textfragment iterator is now invalid, restart from the beginning
            // Take care not to replace the same fragment again, or we would be in
            // an infinite loop.
            cursorPositionsToSkip.insert( pos );
            it = currentBlock.begin();
          }
        }
      }
    }
    currentBlock = currentBlock.next();
  }
}

void TextEditPrivate::addImageHelper( const QString &imageName, const QImage &image,
                                      int width, int height )
{
  QString imageNameToAdd = imageName;
  QTextDocument *document = q->document();

  // determine the imageNameToAdd
  int imageNumber = 1;
  while ( mImageNames.contains( imageNameToAdd ) ) {
    QVariant qv = document->resource( QTextDocument::ImageResource, QUrl( imageNameToAdd ) );
    if ( qv == image ) {
      // use the same name
      break;
    }
    int firstDot = imageName.indexOf( QLatin1Char( '.' ) );
    if ( firstDot == -1 ) {
      imageNameToAdd = imageName + QString::number( imageNumber++ );
    } else {
      imageNameToAdd = imageName.left( firstDot ) + QString::number( imageNumber++ ) +
                       imageName.mid( firstDot );
    }
  }

  if ( !mImageNames.contains( imageNameToAdd ) ) {
    document->addResource( QTextDocument::ImageResource, QUrl( imageNameToAdd ), image );
    mImageNames << imageNameToAdd;
  }
  if ( width != -1 && height != -1 ) {
      QTextImageFormat format;
      format.setName( imageNameToAdd );
      format.setWidth( width );
      format.setHeight( height );
      q->textCursor().insertImage( format );
  } else {
      q->textCursor().insertImage( imageNameToAdd );
  }
  q->enableRichTextMode();
}

ImageWithNameList TextEdit::imagesWithName() const
{
  ImageWithNameList retImages;
  QStringList seenImageNames;
  QList<QTextImageFormat> imageFormats = d->embeddedImageFormats();
  foreach ( const QTextImageFormat &imageFormat, imageFormats ) {
    if ( !seenImageNames.contains( imageFormat.name() ) ) {
      QVariant resourceData = document()->resource( QTextDocument::ImageResource,
                                                    QUrl( imageFormat.name() ) );
      QImage image = qvariant_cast<QImage>( resourceData );
      QString name = imageFormat.name();
      ImageWithNamePtr newImage( new ImageWithName );
      newImage->image = image;
      newImage->name = name;
      retImages.append( newImage );
      seenImageNames.append( imageFormat.name() );
    }
  }
  return retImages;
}

QList< QSharedPointer<EmbeddedImage> > TextEdit::embeddedImages() const
{
  ImageWithNameList normalImages = imagesWithName();
  QList< QSharedPointer<EmbeddedImage> > retImages;
  foreach ( const ImageWithNamePtr &normalImage, normalImages ) {
    QBuffer buffer;
    buffer.open( QIODevice::WriteOnly );
    normalImage->image.save( &buffer, "PNG" );

    qsrand( QDateTime::currentDateTime().toTime_t() + qHash( normalImage->name ) );
    QSharedPointer<EmbeddedImage> embeddedImage( new EmbeddedImage() );
    retImages.append( embeddedImage );
    embeddedImage->image = KMime::Codec::codecForName( "base64" )->encode( buffer.buffer() );
    embeddedImage->imageName = normalImage->name;
    embeddedImage->contentID = QString( QLatin1String( "%1@KDE" ) ).arg( qrand() );
  }
  return retImages;
}

QList<QTextImageFormat> TextEditPrivate::embeddedImageFormats() const
{
  QTextDocument *doc = q->document();
  QList<QTextImageFormat> retList;

  QTextBlock currentBlock = doc->begin();
  while ( currentBlock.isValid() ) {
    QTextBlock::iterator it;
    for ( it = currentBlock.begin(); !it.atEnd(); ++it ) {
      QTextFragment fragment = it.fragment();
      if ( fragment.isValid() ) {
        QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
        if ( imageFormat.isValid() ) {
          //TODO: Replace with a way to see if an image is an embedded image or a remote
          QUrl url( imageFormat.name() );
          if ( !url.isValid() || !url.scheme().startsWith( QLatin1String( "http" ) ) ) {
            retList.append( imageFormat );
          }
        }
      }
    }
    currentBlock = currentBlock.next();
  }
  return retList;
}

void TextEditPrivate::_k_slotAddEmoticon( const QString &text )
{
  QTextCursor cursor = q->textCursor();
  cursor.insertText( text );
}

void TextEditPrivate::_k_slotInsertHtml()
{
  if ( q->textMode() == KRichTextEdit::Rich ) {
    QPointer<InsertHtmlDialog> dialog = new InsertHtmlDialog( q );
    if ( dialog->exec() ) {
      const QString str = dialog->html();
      if ( !str.isEmpty() ) {
        QTextCursor cursor = q->textCursor();
        cursor.insertHtml( str );
      }
    }
    delete dialog;
  }
}

void TextEditPrivate::_k_slotAddImage()
{
  QPointer<InsertImageDialog> dlg = new InsertImageDialog( q );
  if ( dlg->exec() == KDialog::Accepted && dlg ) {
    const KUrl url = dlg->imageUrl();
    int imageWidth = -1;
    int imageHeight = -1;
    if ( !dlg->keepOriginalSize() ) {
        imageWidth = dlg->imageWidth();
        imageHeight = dlg->imageHeight();
    }
    q->addImage( url, imageWidth, imageHeight );
  }
  delete dlg;
}

void TextEditPrivate::_k_slotTextModeChanged( KRichTextEdit::Mode mode )
{
  if ( mode == KRichTextEdit::Rich ) {
    saveFont = q->currentFont();
  }
}

void TextEditPrivate::_k_slotFormatReset()
{
  q->setTextBackgroundColor( q->palette().highlightedText().color() );
  q->setTextForegroundColor( q->palette().text().color() );
  q->setFont( saveFont );

}

void KPIMTextEdit::TextEdit::enableImageActions()
{
  d->imageSupportEnabled = true;
}

bool KPIMTextEdit::TextEdit::isEnableImageActions() const
{
  return d->imageSupportEnabled;
}

void KPIMTextEdit::TextEdit::enableEmoticonActions()
{
  d->emoticonSupportEnabled = true;
}

bool KPIMTextEdit::TextEdit::isEnableEmoticonActions() const
{
  return d->emoticonSupportEnabled;
}

void KPIMTextEdit::TextEdit::enableInsertHtmlActions()
{
  d->insertHtmlSupportEnabled = true;
}

bool KPIMTextEdit::TextEdit::isEnableInsertHtmlActions() const
{
  return d->insertHtmlSupportEnabled;
}

bool KPIMTextEdit::TextEdit::isEnableInsertTableActions() const
{
  return d->insertTableSupportEnabled;
}

void KPIMTextEdit::TextEdit::enableInsertTableActions()
{
  d->insertTableSupportEnabled = true;
}

QByteArray KPIMTextEdit::TextEdit::imageNamesToContentIds(
  const QByteArray &htmlBody, const KPIMTextEdit::ImageList &imageList )
{
  QByteArray result = htmlBody;
  if ( !imageList.isEmpty() ) {
    foreach ( const QSharedPointer<EmbeddedImage> &image, imageList ) {
      const QString newImageName = QLatin1String( "cid:" ) + image->contentID;
      QByteArray quote( "\"" );
      result.replace( QByteArray( quote + image->imageName.toLocal8Bit() + quote ),
                      QByteArray( quote + newImageName.toLocal8Bit() + quote ) );
    }
  }
  return result;
}

void TextEdit::insertImage( const QImage &image, const QFileInfo &fileInfo )
{
  QString imageName = fileInfo.baseName().isEmpty() ?
                        i18nc( "Start of the filename for an image", "image" ) :
                        fileInfo.baseName();
  d->addImageHelper( imageName, image );
}

void TextEdit::insertFromMimeData( const QMimeData *source )
{
  // Add an image if that is on the clipboard
  if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled ) {
    QImage image = qvariant_cast<QImage>( source->imageData() );
    QFileInfo fi( source->text() );
    insertImage( image, fi );
    return;
  }

  // Attempt to paste HTML contents into the text edit in plain text mode,
  // prevent this and prevent plain text instead.
  if ( textMode() == KRichTextEdit::Plain && source->hasHtml() ) {
    if ( source->hasText() ) {
      insertPlainText( source->text() );
      return;
    }
  }

  KRichTextWidget::insertFromMimeData( source );
}

bool KPIMTextEdit::TextEdit::canInsertFromMimeData( const QMimeData *source ) const
{
  if ( source->hasHtml() && textMode() == KRichTextEdit::Rich ) {
    return true;
  }

  if ( source->hasText() ) {
    return true;
  }

  if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled ) {
    return true;
  }

  return KRichTextWidget::canInsertFromMimeData( source );
}

bool TextEdit::isFormattingUsed() const
{
  if ( textMode() == Plain ) {
    return false;
  }

  return TextUtils::containsFormatting( document() );
}

void TextEditPrivate::_k_slotDeleteLine()
{
  if ( q->hasFocus() ) {
    q->deleteCurrentLine();
  }
}

void TextEdit::deleteCurrentLine()
{
  QTextCursor cursor = textCursor();
  QTextBlock block = cursor.block();
  const QTextLayout *layout = block.layout();

  // The current text block can have several lines due to word wrapping.
  // Search the line the cursor is in, and then delete it.
  for ( int lineNumber = 0; lineNumber < layout->lineCount(); lineNumber++ ) {
    QTextLine line = layout->lineAt( lineNumber );
    const bool lastLineInBlock = ( line.textStart() + line.textLength() == block.length() - 1 );
    const bool oneLineBlock = ( layout->lineCount() == 1 );
    const int startOfLine = block.position() + line.textStart();
    int endOfLine = block.position() + line.textStart() + line.textLength();
    if ( !lastLineInBlock ) {
      endOfLine -= 1;
    }

    // Found the line where the cursor is in
    if ( cursor.position() >= startOfLine && cursor.position() <= endOfLine ) {
      int deleteStart = startOfLine;
      int deleteLength = line.textLength();
      if ( oneLineBlock ) {
        deleteLength++; // The trailing newline
      }

      // When deleting the last line in the document,
      // remove the newline of the line before the last line instead
      if ( deleteStart + deleteLength >= document()->characterCount() &&
           deleteStart > 0 ) {
        deleteStart--;
      }

      cursor.beginEditBlock();
      cursor.setPosition( deleteStart );
      cursor.movePosition( QTextCursor::NextCharacter, QTextCursor::KeepAnchor, deleteLength );
      cursor.removeSelectedText();
      cursor.endEditBlock();
      return;
    }
  }
}

#include "moc_textedit.cpp"
